/*
 * mempool.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version mempool.c 1.0
 * @package libbspcore
 * @author Dr.NP <np@bsgroup.org>
 * @update 10/09/2011
 */

/**
 * Static memory pool
 * 
 * === CHANGELOG ===
 * [10/09/2011] - Creation
 */

#include "bsp.h"

#include "bsp_mempool.h"
#include "bsp_utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <err.h>
#include <math.h>
#include <pthread.h>
#include <sys/types.h>

struct slab_t *slab_link = NULL;
size_t slab_link_size;
static pthread_mutex_t mempool_lock = PTHREAD_MUTEX_INITIALIZER;

int get_slab_id();
size_t get_slab_item_size(int);
int init_slab_node(int, struct slab_t *);

/**
 * Initialization of mempool
 *
 * @return int                                  Return value
 */
int mempool_init()
{
    slab_link = ensure_list_space(NULL, sizeof(struct slab_t), &slab_link_size, SLAB_LINK_INITIAL);
    if (!slab_link)
    {
        return RTN_ERROR_MEMORY_ALLOC;
    }
    
    memset(slab_link, 0, sizeof(struct slab_t) * SLAB_LINK_INITIAL);
    
    return RTN_SUCCESS;
}

/**
 * Initialize a slab node
 * Include freelist and memory block
 *
 * @param int slab_id                           Slab ID
 * @param slab_t                                Pointor to the node
 *
 * @return int                                  Return value
 */
int init_slab_node(int slab_id, struct slab_t *s)
{
    void *tmp;
    
    if (!s)
    {
        return RTN_ERROR_FATAL;
    }
    
    if (!s->block_list || s->block_list_size == 0)
    {
        s->block_list = ensure_list_space(NULL, sizeof(void *), &s->block_list_size, BLOCK_LIST_INITIAL);
    }
    
    if (!s->block_list)
    {
        err(RTN_ERROR_MEMORY_ALLOC, "Memory pool alloc error!");
    }

    s->block_list_data_size = 0;
    tmp = malloc(BLOCK_SIZE);
    
    if (tmp)
    {
        s->block_list[0] = tmp;
        s->block_list_data_size = 1;
    }
    
    if (!s->free_list)
    {
        s->free_list = ensure_list_space(NULL, sizeof(void *), &s->free_list_size, SLAB_FREE_LIST_INITIAL);
        
        if (!s->free_list)
        {
            err(RTN_ERROR_MEMORY_ALLOC, "Memory pool alloc error!\n");
        }
    }
    
    s->free_list_data_size = 0;
    s->slab_id = slab_id;
    s->item_size = get_slab_item_size(slab_id);
    s->item_capacity = BLOCK_SIZE / s->item_size;
    s->item_curr_total = 0;
    
    return RTN_SUCCESS;
}

/**
 * Calculate the slab id by given size
 *
 * @param int nsize                             Item size
 *
 * @return int                                  Slab ID
 */
int get_slab_id(int nsize)
{
    int slab_id, base2;
    float index, remain;
    
    if (nsize <= SLAB_SIZE_BASE)
    {
        slab_id = 0;
    }
    
    // Slab item size = 1.25 * 1.25 * 1.28 * 1.25 * 1.25 * 1.28 ...
    else
    {
        index = (float) nsize / SLAB_SIZE_BASE;
        base2 = log2(index);
        remain = (float) index / (pow(2, base2));
        slab_id = 3 * base2;
        
        if (remain > 1.5625f)
        {
            slab_id += 3;
        }
        
        else if (remain > 1.25f)
        {
            slab_id += 2;
        }
        
        else if (remain > 1)
        {
            slab_id += 1;
        }
    }
    
    return slab_id;
}

/**
 * Get Item size by given slab ID
 *
 * @param int slab_id                           Slab ID
 *
 * @return int                                  Item size(include header)
 */
size_t get_slab_item_size(int slab_id)
{
    size_t rtn = 0;
    int base2 = slab_id / 3;
    int m2 = slab_id % 3;
    size_t rt2 = SLAB_SIZE_BASE * pow(2, base2);
    
    rtn = rt2 * pow(1.25f, m2);
    
    return rtn;
}

/**
 * Fetch space from pool
 *
 * @param int                                   Length required
 *
 * @return void *                               Memory address
 */
void * mempool_alloc(size_t nsize)
{
    void *ret = NULL, *block_node = NULL;
    size_t osize = nsize + 4;
    struct slab_t *tmp = NULL;
    int i;
    
    if (!slab_link)
    {
        // Not initialized
        return ret;
    }
    
    int slab_id = get_slab_id(osize);
    
    pthread_mutex_lock(&mempool_lock);
    
    // Enlarge slab_link
    while (slab_link_size < slab_id)
    {
        slab_link = ensure_list_space(slab_link, sizeof(struct slab_t), &slab_link_size, slab_link_size * 2);
        if (!slab_link)
        {
            err(RTN_ERROR_MEMORY_ALLOC, "Memory pool alloc error!\n");
        }
        
        for (i = slab_link_size / 2; i < slab_link_size; i ++)
        {
            slab_link[i].free_list = NULL;
            slab_link[i].item_capacity = 0;
        }
    }
    
    tmp = &slab_link[slab_id];
    if (!tmp->item_capacity)
    {
        // Add the first block
        init_slab_node(slab_id, tmp);
    }
    
    if (tmp->free_list_data_size > 0)
    {
        // Just popup
        ret = tmp->free_list[-- tmp->free_list_data_size];
    }
    
    else
    {
        // No free item, add a new one
        if (tmp->item_curr_total < tmp->item_capacity)
        {
            block_node = tmp->block_list[tmp->block_list_data_size - 1];
            if (block_node)
            {
                // Next item
                ret = block_node + (tmp->item_curr_total * tmp->item_size);
                tmp->item_curr_total ++;
            }
        }
        
        else
        {
            // Need a new block
            block_node = malloc(BLOCK_SIZE);
            if (block_node)
            {
                while (tmp->block_list_data_size >= tmp->block_list_size)
                {
                    // enlarge block list
                    tmp->block_list = ensure_list_space(tmp->block_list, sizeof(void *), &tmp->block_list_size, tmp->block_list_size * 2);
                    if (!tmp->block_list)
                    {
                        err(RTN_ERROR_MEMORY_ALLOC, "Memory pool alloc error!\n");
                    }
                    
                    //memset(tmp->block_list + sizeof(void *) * tmp->block_list_size / 2, 0, sizeof(void *) * tmp->block_list_size / 2);
                    
                }
                tmp->block_list[tmp->block_list_data_size ++] = block_node;
                tmp->item_curr_total = 1;
                ret = block_node;
                
                //debug_info("A new memory block [%d] alloced\n", tmp->block_list_data_size - 1);
            }
        }
    }
    
    pthread_mutex_unlock(&mempool_lock);
    
    if (ret)
    {
        put_u_int(ret, slab_id);
        ret += 4;
        
        //debug_info("Memory pool : item alloced at slab %d : request size %d\n", slab_id, nsize);
    }
    
    return ret;
}

/**
 * Return space to free list
 *
 * @param void addr                             Address to free
 *
 * @return int                                  Return value
 */
int mempool_free(void *addr)
{
    struct slab_t *tmp;
    
    if (!addr)
    {
        return RTN_ERROR_GENERAL;
    }
    
    int slab_id = get_u_int(addr - 4);
    
    if (slab_id >=0 && slab_id < slab_link_size)
    {
        // Return to free list
        tmp = &slab_link[slab_id];
        
        if (!tmp->free_list)
        {
            init_slab_node(slab_id, tmp);
        }
        
        while (tmp->free_list_data_size >= tmp->free_list_size)
        {
            // Enlarge list
            tmp->free_list = ensure_list_space(tmp->free_list, sizeof(void *), &tmp->free_list_size, tmp->free_list_size * 2);
            
            if (!tmp->free_list)
            {
                err(RTN_ERROR_MEMORY_ALLOC, "Memory pool alloc error!\n");
            }
            
            memset(tmp->free_list + sizeof(void *) * tmp->free_list_size / 2, 0, sizeof(void *) * tmp->free_list_size / 2);
        }
        
        tmp->free_list[tmp->free_list_data_size ++] = addr;
        
        //debug_info("Memory pool : item recycled at slab %d\n", slab_id);
    }
    
    return RTN_SUCCESS;
}

/**
 * Realloc a memory space
 * 
 * @param void *                                Target address
 * @param size                                  New size to alloc
 * 
 * @return void *                               New address
 */
void * mempool_realloc(void *addr, size_t nsize)
{
    if (!addr)
    {
        return NULL;
    }
    
    int slab_id = get_u_int(addr - 4);
    size_t slab_item_size = get_slab_item_size(slab_id);
    if (nsize <= slab_item_size)
    {
        // Needn't move data, just return the same addr
        return addr;
    }
    
    else
    {
        // Move data to a new slab
        void *new_addr = mempool_alloc(nsize);
        memcpy(new_addr, addr, slab_item_size);
        mempool_free(addr);
        
        return new_addr;
    }
    
    return NULL;
}
